"""
High-performance web server for Claude Code workdir statistics.
"""

import json
import time
import logging
import asyncio
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from pathlib import Path

from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn

from .core import WorkdirStatsCollector, SessionInfo


class StatsCache:
    """High-performance caching for workdir statistics."""
    
    def __init__(self, ttl_seconds: int = 30):
        self.ttl_seconds = ttl_seconds
        self.cache: Dict[str, Dict[str, Any]] = {}
        self.last_update: Dict[str, float] = {}
    
    def get(self, key: str) -> Optional[Dict[str, Any]]:
        """Get cached data if still valid."""
        if key not in self.cache:
            return None
        
        if time.time() - self.last_update.get(key, 0) > self.ttl_seconds:
            # Cache expired
            del self.cache[key]
            del self.last_update[key]
            return None
        
        return self.cache[key]
    
    def set(self, key: str, data: Dict[str, Any]):
        """Cache data with timestamp."""
        self.cache[key] = data
        self.last_update[key] = time.time()
    
    def clear(self):
        """Clear all cached data."""
        self.cache.clear()
        self.last_update.clear()


class WebSocketManager:
    """Manages WebSocket connections for real-time updates."""
    
    def __init__(self):
        self.active_connections: list[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        if websocket in self.active_connections:
            self.active_connections.remove(websocket)
    
    async def broadcast(self, message: dict):
        """Broadcast message to all connected clients."""
        if not self.active_connections:
            return
        
        disconnected = []
        for connection in self.active_connections:
            try:
                await connection.send_json(message)
            except Exception:
                disconnected.append(connection)
        
        # Remove disconnected clients
        for connection in disconnected:
            self.disconnect(connection)


# Global instances
app = FastAPI(title="Claude Code Workdir Monitor", version="1.0.0")
cache = StatsCache(ttl_seconds=30)
ws_manager = WebSocketManager()

# Global config
config = {
    "claude_dir": None,
    "hours_back": None
}


def serialize_stats(stats_list):
    """Serialize WorkdirStats objects to JSON-compatible format."""
    json_data = []
    for stats in stats_list:
        json_data.append({
            'workdir': stats.workdir,
            'active_sessions': stats.active_sessions,
            'total_tokens': stats.total_tokens,
            'input_tokens': stats.input_tokens,
            'output_tokens': stats.output_tokens,
            'cache_creation_tokens': stats.cache_creation_tokens,
            'cache_read_tokens': stats.cache_read_tokens,
            'total_cost': stats.total_cost,
            'last_activity': stats.last_activity.isoformat() if stats.last_activity else None,
            'session_count': stats.session_count,
            'models_used': stats.models_used,
            'is_active': bool(stats.active_sessions)
        })
    return json_data


def get_stats_data(hours_back: Optional[int] = None, show_inactive: bool = True):
    """Get workdir statistics with caching."""
    cache_key = f"stats_{hours_back}_{show_inactive}"
    
    # Try cache first
    cached_data = cache.get(cache_key)
    if cached_data:
        return cached_data
    
    # Generate fresh data
    collector = WorkdirStatsCollector(
        claude_dir=config["claude_dir"],
        hours_back=hours_back or config["hours_back"]
    )
    
    stats_list = collector.collect_all_workdir_stats()
    
    if not show_inactive:
        stats_list = [s for s in stats_list if s.active_sessions]
    
    data = {
        'stats': serialize_stats(stats_list),
        'total_count': len(stats_list),
        'active_count': sum(1 for s in stats_list if s.active_sessions),
        'total_tokens': sum(s.total_tokens for s in stats_list),
        'total_cost': sum(s.total_cost for s in stats_list),
        'last_updated': datetime.now().isoformat()
    }
    
    # Cache the result
    cache.set(cache_key, data)
    return data


# Setup templates and static files
templates_dir = Path(__file__).parent / "templates"
static_dir = Path(__file__).parent / "static"

# Create directories if they don't exist
templates_dir.mkdir(exist_ok=True)
static_dir.mkdir(exist_ok=True)

templates = Jinja2Templates(directory=str(templates_dir))
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")


@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    """Main dashboard page."""
    return templates.TemplateResponse("dashboard.html", {"request": request})


@app.get("/api/stats")
async def get_stats(
    hours_back: Optional[int] = None, 
    show_inactive: bool = True,
    workdir: Optional[str] = None
):
    """API endpoint for statistics data."""
    try:
        data = get_stats_data(hours_back, show_inactive)
        
        if workdir:
            # Filter to specific workdir
            data['stats'] = [s for s in data['stats'] if s['workdir'] == workdir]
            if not data['stats']:
                raise HTTPException(status_code=404, detail=f"No activity found for workdir: {workdir}")
        
        return data
    except Exception as e:
        logging.error(f"Error getting stats: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/summary")
async def get_summary():
    """Get summary statistics."""
    try:
        data = get_stats_data()
        return {
            'total_workdirs': data['total_count'],
            'active_workdirs': data['active_count'],
            'total_tokens': data['total_tokens'],
            'total_cost': data['total_cost'],
            'last_updated': data['last_updated']
        }
    except Exception as e:
        logging.error(f"Error getting summary: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/workdir/{workdir:path}/sessions")
async def get_workdir_sessions(workdir: str):
    """Get sessions for a specific workdir."""
    try:
        collector = WorkdirStatsCollector(
            claude_dir=config["claude_dir"],
            hours_back=config["hours_back"]
        )
        
        sessions = collector.get_workdir_sessions(workdir)
        
        # Serialize sessions to JSON-compatible format
        sessions_data = []
        for session in sessions:
            sessions_data.append({
                'session_id': session.session_id,
                'start_time': session.start_time.isoformat() if session.start_time else None,
                'end_time': session.end_time.isoformat() if session.end_time else None,
                'is_active': session.is_active,
                'total_tokens': session.total_tokens,
                'input_tokens': session.input_tokens,
                'output_tokens': session.output_tokens,
                'cache_creation_tokens': session.cache_creation_tokens,
                'cache_read_tokens': session.cache_read_tokens,
                'total_cost': session.total_cost,
                'models_used': session.models_used,
                'first_user_prompt': session.first_user_prompt,
                'abstract': session.abstract
            })
        
        return {
            'workdir': workdir,
            'sessions': sessions_data,
            'total_sessions': len(sessions_data),
            'active_sessions': sum(1 for s in sessions if s.is_active)
        }
    except Exception as e:
        logging.error(f"Error getting sessions for workdir {workdir}: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket endpoint for real-time updates."""
    await ws_manager.connect(websocket)
    try:
        while True:
            # Send updated data every 30 seconds
            await asyncio.sleep(30)
            
            # Clear cache to force fresh data
            cache.clear()
            data = get_stats_data()
            
            await websocket.send_json({
                'type': 'stats_update',
                'data': data
            })
    except WebSocketDisconnect:
        ws_manager.disconnect(websocket)
    except Exception as e:
        logging.error(f"WebSocket error: {e}")
        ws_manager.disconnect(websocket)


@app.get("/health")
async def health_check():
    """Health check endpoint."""
    return {"status": "healthy", "timestamp": datetime.now().isoformat()}


def start_server(
    claude_dir: Optional[str] = None,
    hours_back: Optional[int] = None,
    host: str = "localhost",
    port: int = 8080,
    debug: bool = False,
    force: bool = False
):
    """Start the web server."""
    import os
    import atexit
    from .utils import write_pid_file, remove_pid_file, check_port_in_use, read_pid_file, is_process_running
    
    # Clean up stale PID files first
    existing_pid = read_pid_file(port)
    if existing_pid and not is_process_running(existing_pid):
        remove_pid_file(port)
        print(f"🧹 Cleaned up stale PID file for port {port}")
    
    # Check if port is already in use (unless force is enabled)
    if not force and check_port_in_use(host, port):
        # Check if it's our own server
        existing_pid = read_pid_file(port)
        if existing_pid and is_process_running(existing_pid):
            print(f"❌ ccwork server is already running on port {port}. Use --stop to stop it first.")
        else:
            print(f"❌ Port {port} appears to be in use by another process.")
            print(f"   Use --force to start anyway, or choose a different port.")
        return 1
    
    if force:
        print(f"⚠️  Force mode enabled - attempting to start on port {port}")
    
    # Set global config
    config["claude_dir"] = claude_dir
    config["hours_back"] = hours_back
    
    # Write PID file
    pid = os.getpid()
    write_pid_file(port, pid)
    
    # Ensure PID file is cleaned up on exit
    def cleanup():
        remove_pid_file(port)
    atexit.register(cleanup)
    
    # Create template and static files if they don't exist
    create_frontend_files()
    
    print(f"🚀 Starting Claude Code Workdir Monitor")
    print(f"📊 Dashboard: http://{host}:{port}")
    print(f"🔌 API: http://{host}:{port}/api/stats")
    print(f"❤️  Health: http://{host}:{port}/health")
    print(f"🛑 Stop server: ccwork --stop --port {port}")
    print(f"Press Ctrl+C to stop")
    
    try:
        uvicorn.run(
            app,
            host=host,
            port=port,
            log_level="debug" if debug else "info",
            access_log=debug
        )
    except KeyboardInterrupt:
        print(f"\n🛑 Server stopped gracefully")
    finally:
        cleanup()


def create_frontend_files():
    """Create HTML template and static files."""
    # Files are already created in templates/ directory
    pass